/**
 * This class sits between the agent manager and the 
 * connection manager. It provides an incoming and an
 * outgoing queue using vectors, and implements the 
 * agents toString and fromString methods as required. 
 * 
 * @author Aidan
 */
package ap;
import java.util.Vector;
import comms.ConnectionManager;
import agent.*;

public class AgentPlatform implements Runnable{
	private manager.IOHandlerManager agentManager;
	private ConnectionManager connectionManager;
	private Vector inAgent;
	private Vector outAgent;
	private AgentPkt agentPkt;
	private String udpPort;
	private String tcpPort;
	private String mmsID;
	private boolean quit;    
	/**
	 * constructor
	 * @param am: reference back to the agent manager that instantiated this object 
	 * @param udpPort: the UDP port the connection manager should listen on
	 * @param tcpPort: the TCP port the connection manager should listen on
	 * @param mmsID: the MMS ID number the connection manager should listen on
	 */
	public AgentPlatform(manager.IOHandlerManager am, String udpPort, String tcpPort, String mmsID){
		this.agentManager = am;
		
		this.inAgent = new Vector();
		this.outAgent = new Vector();
		this.agentPkt = new AgentPkt();
		this.udpPort = udpPort;
		this.tcpPort = tcpPort;
		this.mmsID = mmsID;
		this.quit = false;
		this.start();
	}
	/**
	 * used to start this agent platform thread
	 */
	public void start() {
		Thread t = new Thread(this);
		t.start();
	}
	/**
	 * wait for agents to be set from the agent manager or the connection manager
	 * then translate the string/agent 
	 */
	public synchronized void run(){
		try{			
			this.connectionManager = new ConnectionManager(this, this.udpPort, this.tcpPort, this.mmsID);
		}catch (NullPointerException e){
			System.err.println("Null Pointer Exception thrown in AgentPlatform.run() " +
					"when trying to create connection manager");
			e.printStackTrace();
		}
		while(!quit){			
			//if incoming and outgoing queues are empty: wait
			if(this.inAgent.isEmpty()&& this.outAgent.isEmpty()) {
				try{
					System.err.println("Platform: waiting");
					wait();
				}catch(InterruptedException e){
					System.err.println("Interrupted Exceprion thrown in AgentPlatform.run.wait()");
					e.printStackTrace();
				}catch (NullPointerException e){
					System.err.println("Null Pointer Exception thrown in AgentPlatform.run.wait()");
					e.printStackTrace();
				}
			}
			//if incoming queue is not empty notify AgentManager of new agent 
			else if(!this.inAgent.isEmpty()) {
				if(!this.agentManager.isAgentWaiting()){
					this.agentManager.agentWaiting();				
				}
				try {
					wait();
				} catch (InterruptedException e) {
					System.err.println("Interrupted Exceprion thrown in AgentPlatform.run.wait()");
					e.printStackTrace();
				} catch (NumberFormatException e){
					System.err.println("Number Format Exception thrown in AgentPlatform.run.wait()");
					e.printStackTrace();
				}
			}		
			//if outgoing queue is not empty create a new agent packet and forward it to the ConnectionManager
			else if(!this.outAgent.isEmpty()) {	
				this.agentPkt = (AgentPkt)outAgent.firstElement();
				this.outAgent.removeElementAt(0);				
				this.connectionManager.forwardAgent(agentPkt);
			}
		}	
	}
	/****************************************************************
	 * access methods used by agent manager and connection manager
	 ****************************************************************
	 * getInAgent called by agent manager to pass up received agent
	 * 
	 * @return the agent from the inAgent vector
	 */
	public synchronized Agent getInAgent() throws NoSuchElementException{
		if(inAgent.isEmpty())
			throw new NoSuchElementException("inAgent vector Empty");
		else{
			Agent agent;		
			agent = (Agent)inAgent.firstElement();
			inAgent.removeElementAt(0);
			
			return agent;			
		}
	}
	/****************************************************************
	 * setOutAgent called by agent manager to set agent to be sent out
	 * 		increment nextAddressIndex
	 * 		set agentPkt destination address
	 * 		set agentPkt destination port
	 * 	 	set agentPkt agent string
	 * @param outAgent: the outAgent to set
	 */
	public synchronized void setOutAgent(Agent outAgent) {		
		outAgent.incrementToNextAddress();
		this.agentPkt.setDestAddress(outAgent.getNextAddress());
		this.agentPkt.setDestPort(outAgent.getNextPort());
		this.agentPkt.setAgentString(outAgent.toString());			
	
		this.outAgent.addElement(agentPkt);		
		notify();
	}		
	/****************************************************************
	 * called by connection manager to set the received agent.
	 * extract the agent type with the tokenizer, create a
	 * new agent of required type and add to incoming queue.
	 * finally notify the run method to stop it waiting
	 * 
	 * @param add the agent to inAgent vector
	 */
	public synchronized void setInAgent(String agentString) {
		Tokenizer tokenizer = new Tokenizer(agentString,"|");
		String token = "";

		try{
			token = tokenizer.nextToken();
		}catch(NoSuchElementException e){
			System.err.println("No Such Element Exception thrown in AgentPlatform.setInAgent()");
			e.printStackTrace();
		}
		try{
			if(token.equals(Agent.TYPE_MEETING)){
				MeetingAgent agent = new MeetingAgent();
				agent.fromString(agentString);
				this.inAgent.addElement(agent);
				agent.printAvailability();
			}
			else if(token.equals(Agent.TYPE_INFORMATION)){
				System.err.println("AgentPlatform.setInAgent() AGENT STRING: " + agentString);
				InfoAgent agent = new InfoAgent();
				agent.fromString(agentString);
				this.inAgent.addElement(agent);
			}		
		}catch (NumberFormatException e){
			System.err.println("Number Format Exception thrown in AgentPlatform.setInAgent()");
			e.printStackTrace();
		}
				
		notify();
	}
	/****************************************************************
	 * method to stop the current agent platform thread
	 */
	public synchronized void stop() {
		this.quit = true;
		notify();
	}
}